summaryrefslogtreecommitdiff
path: root/app/api/auth/[...nextauth]/saml/provider.ts
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-06-23 06:44:34 +0000
committerjoonhoekim <26rote@gmail.com>2025-06-23 06:44:34 +0000
commitebe273ef4564d55f9bf193adc51a9e58211e72e9 (patch)
tree30e8c48be41d14751eceb4c24d88c18d03e9102b /app/api/auth/[...nextauth]/saml/provider.ts
parentabd9f950bbd95b9ad713a26d3fd8a7e0282b7c51 (diff)
(김준회 SAML 2.0 SSO 리팩터링, 디버깅 유틸리티 추가, MOCK 처리 추가
Diffstat (limited to 'app/api/auth/[...nextauth]/saml/provider.ts')
-rw-r--r--app/api/auth/[...nextauth]/saml/provider.ts159
1 files changed, 143 insertions, 16 deletions
diff --git a/app/api/auth/[...nextauth]/saml/provider.ts b/app/api/auth/[...nextauth]/saml/provider.ts
index 92099be0..1f891661 100644
--- a/app/api/auth/[...nextauth]/saml/provider.ts
+++ b/app/api/auth/[...nextauth]/saml/provider.ts
@@ -1,5 +1,9 @@
import CredentialsProvider from "next-auth/providers/credentials"
import { getOrCreateSAMLUser, validateSAMLUserData } from '@/lib/users/saml-service'
+import { encode } from 'next-auth/jwt'
+import type { User } from 'next-auth'
+import type { SAMLUser } from './utils'
+import { debugLog, debugError, debugSuccess, debugProcess } from '@/lib/debug-utils'
interface SAMLProviderOptions {
id: string
@@ -28,59 +32,80 @@ export function SAMLProvider(options: SAMLProviderOptions) {
}
},
async authorize(credentials) {
+ debugLog('🔍 SAMLProvider.authorize called with credentials:', credentials);
+
try {
+ debugLog('🔍 Checking credentials.user:', {
+ hasCredentials: !!credentials,
+ hasUser: !!credentials?.user,
+ userType: typeof credentials?.user,
+ userValue: credentials?.user?.substring?.(0, 100) + '...'
+ });
+
if (!credentials?.user) {
- console.error('No user data provided')
+ debugError('No user data provided in credentials')
return null
}
- console.log('🔐 SAML Provider: Processing user data')
+ debugProcess('SAML Provider: Processing user data')
// 사용자 데이터 파싱 (UTF-8 처리 개선)
const userDataString = credentials.user
- console.log('🔤 Raw user data string:', userDataString.substring(0, 200) + '...')
+ debugLog('🔤 Raw user data string:', userDataString.substring(0, 200) + '...')
- const userData = JSON.parse(userDataString)
+ let userData;
+ try {
+ userData = JSON.parse(userDataString);
+ debugSuccess('JSON parsing successful:', userData);
+ } catch (parseError) {
+ debugError('JSON parsing failed:', parseError);
+ debugError('Raw string that failed to parse:', userDataString);
+ return null;
+ }
// 파싱된 데이터의 UTF-8 확인
- console.log('🔤 Parsed user data UTF-8 check:', {
+ debugLog('🔤 Parsed user data UTF-8 check:', {
name: userData.name,
nameLength: userData.name?.length,
charCodes: userData.name ? [...userData.name].map(c => c.charCodeAt(0)) : []
})
if (!userData.id || !userData.email) {
- console.error('Invalid SAML user data:', userData)
+ debugError('Invalid SAML user data:', userData)
return null
}
- console.log('✅ SAML Provider: User authenticated successfully', {
+ debugSuccess('SAML Provider: User authenticated successfully', {
id: userData.id,
email: userData.email,
name: userData.name
})
// 🔥 SAML 사용자 데이터 검증
+ debugProcess('Validating SAML user data structure...');
const isValidData = await validateSAMLUserData(userData)
+ debugLog('Validation result:', isValidData);
if (!isValidData) {
- console.error('Invalid SAML user data structure:', userData)
+ debugError('Invalid SAML user data structure:', userData)
return null
}
// 🔥 JIT (Just-In-Time) 사용자 생성 또는 조회
- const dbUser = await getOrCreateSAMLUser({
+ debugProcess('Creating/getting SAML user from database...');
+ const userCreateData = {
email: userData.email,
name: userData.name,
- // companyId: userData.companyId,
- // techCompanyId: userData.techCompanyId,
- // ! domain = evcp 이면 vendor가 갖는 companyId, techCompanyId는 null
companyId: undefined,
techCompanyId: undefined,
domain: userData.domain
- })
+ };
+ debugLog('User create data:', userCreateData);
+
+ const dbUser = await getOrCreateSAMLUser(userCreateData)
+ debugLog('Database user result:', dbUser);
if (!dbUser) {
- console.error('Failed to get or create SAML user')
+ debugError('Failed to get or create SAML user')
return null
}
@@ -95,10 +120,15 @@ export function SAMLProvider(options: SAMLProviderOptions) {
imageUrl: dbUser.imageUrl, // DB의 실제 이미지 URL
}
- console.log('✅ SAML Provider: Returning user data to NextAuth:', userResult)
+ debugSuccess('SAML Provider: Returning user data to NextAuth:', userResult)
return userResult
} catch (error) {
- console.error('❌ SAML Provider: Authentication failed', error)
+ debugError('SAML Provider: Authentication failed', {
+ error: error instanceof Error ? error.message : String(error),
+ stack: error instanceof Error ? error.stack : undefined,
+ errorType: typeof error,
+ credentials: credentials
+ });
return null
}
}
@@ -125,4 +155,101 @@ export function validateSAMLOptions(options: SAMLProviderOptions): boolean {
return required.every(field => field && field.length > 0)
}
+
+// SAMLProvider의 authorize 함수를 직접 호출하기 위한 헬퍼
+export async function authenticateSAMLUser(userData: SAMLUser) {
+ debugLog('authenticateSAMLUser called with:', userData);
+
+ try {
+ // SAMLProvider 대신 직접 로직 실행 (Provider 래퍼 없이)
+ debugProcess('SAML User Authentication: Processing user data')
+
+ // 사용자 데이터 검증
+ if (!userData.id || !userData.email) {
+ debugError('Invalid SAML user data:', userData)
+ return null
+ }
+
+ debugSuccess('SAML User data validated successfully', {
+ id: userData.id,
+ email: userData.email,
+ name: userData.name
+ })
+
+ // 🔥 SAML 사용자 데이터 검증
+ debugLog('Validating SAML user data structure...');
+ const isValidData = await validateSAMLUserData(userData)
+ debugLog('Validation result:', isValidData);
+ if (!isValidData) {
+ debugError('Invalid SAML user data structure:', userData)
+ return null
+ }
+
+ // 🔥 JIT (Just-In-Time) 사용자 생성 또는 조회
+ debugLog('Creating/getting SAML user from database...');
+ const userCreateData = {
+ email: userData.email,
+ name: userData.name,
+ companyId: undefined,
+ techCompanyId: undefined,
+ domain: userData.domain
+ };
+ debugLog('User create data:', userCreateData);
+
+ const dbUser = await getOrCreateSAMLUser(userCreateData)
+ debugLog('Database user result:', dbUser);
+
+ if (!dbUser) {
+ debugError('Failed to get or create SAML user')
+ return null
+ }
+
+ // DB에서 가져온 실제 사용자 정보 반환
+ const userResult = {
+ id: String(dbUser.id), // DB의 실제 ID
+ name: dbUser.name, // DB의 실제 이름
+ email: dbUser.email, // DB의 실제 이메일
+ companyId: dbUser.companyId, // DB의 실제 회사 ID
+ techCompanyId: dbUser.techCompanyId, // DB의 실제 기술회사 ID
+ domain: dbUser.domain, // DB의 실제 도메인
+ imageUrl: dbUser.imageUrl, // DB의 실제 이미지 URL
+ }
+
+ debugSuccess('SAML User Authentication completed:', userResult)
+ return userResult;
+
+ } catch (error) {
+ debugError('authenticateSAMLUser error:', {
+ error: error instanceof Error ? error.message : String(error),
+ stack: error instanceof Error ? error.stack : undefined,
+ userData
+ });
+ return null;
+ }
+}
+
+// NextAuth JWT 토큰 생성 헬퍼
+export async function createNextAuthToken(user: User): Promise<string> {
+ const token = {
+ id: user.id,
+ email: user.email,
+ name: user.name,
+ companyId: user.companyId,
+ techCompanyId: user.techCompanyId,
+ domain: user.domain,
+ imageUrl: user.imageUrl,
+ iat: Math.floor(Date.now() / 1000),
+ exp: Math.floor(Date.now() / 1000) + (30 * 24 * 60 * 60) // 30일
+ };
+
+ const secret = process.env.NEXTAUTH_SECRET!;
+ return await encode({ token, secret });
+}
+
+// NextAuth 세션 쿠키 이름 가져오기
+export function getSessionCookieName(): string {
+ return process.env.NODE_ENV === 'production'
+ ? '__Secure-next-auth.session-token'
+ : 'next-auth.session-token';
+}
\ No newline at end of file